SPDX-FileCopyrightText: 2022 Mario Hidalgo Venegas & Lucas Van Melderen SPDX-FileCopyrightText: 2024 AlICe laboratory https://alicelab.be
SPDX-License-Identifier: GPL-3.0-or-later
18/01/2023 Blender version 3.3.0
                                                                    #
Mario Hidalgo & Lucas Van Melderen - Arvo Pärt : Spiegel im Spiegel # # Python Code # # AIM 2022-23 / AlICe lab / Final Review 19.01 # #
Here are the general definitions and inputs, the code begin at line : 70
import bpy, random, bmeshdef Clean():
    RemoveAll = bpy.context.copy()
    RemoveAll["selected_objects"] = list(bpy.context.scene.objects)
    bpy.ops.object.delete(RemoveAll)def Deselect_All():
    bpy.ops.object.select_all(action="DESELECT")def Select_obj(Selected):
    bpy.data.objects[Selected].select_set(state=True)def Select_and_Active_obj(Actived):
    bpy.ops.object.select_all(action="DESELECT")
    bpy.context.view_layer.objects.active = bpy.context.view_layer.objects[Actived]
    bpy.context.active_object.select_set(state=True)def Edit_Mode():
    bpy.ops.object.editmode_toggle()def New_loc(obj_loc, lx, ly, lz):
    bpy.data.objects[obj_loc].location.x = lx
    bpy.data.objects[obj_loc].location.y = ly
    bpy.data.objects[obj_loc].location.z = lzdef Resize(rx, ry, rz):
    bpy.ops.transform.resize(value=(rx, ry, rz))def Translate(tx, ty, tz):
    bpy.ops.transform.translate(value=(tx, ty, tz))def Extrude_malifold(ext_dim):
    Edit_Mode()
    bpy.ops.mesh.extrude_manifold(
        MESH_OT_extrude_region={
            "use_normal_flip": False,
            "use_dissolve_ortho_edges": True,
            "mirror": False,
        },
        TRANSFORM_OT_translate={
            "value": (0, -1.19209e-07, ext_dim),
            "orient_axis_ortho": "X",
            "orient_type": "GLOBAL",
            "orient_matrix": (
                (9.30786e-06, -0.99797, -0.0636786),
                (0.987873, -0.0098779, 0.154951),
                (-0.155265, -0.0629079, 0.985868),
            ),
            "orient_matrix_type": "NORMAL",
            "constraint_axis": (True, True, True),
            "mirror": False,
            "use_proportional_edit": False,
            "proportional_edit_falloff": "SMOOTH",
            "proportional_size": 1,
            "use_proportional_connected": False,
            "use_proportional_projected": False,
            "snap": False,
            "snap_elements": {"INCREMENT"},
            "use_snap_project": False,
            "snap_target": "CLOSEST",
            "use_snap_self": True,
            "use_snap_edit": True,
            "use_snap_nonedit": True,
            "use_snap_selectable_only": False,
            "use_snap_to_same_target": False,
            "snap_face_nearest_steps": 1,
            "snap_point": (0, 0, 0),
            "snap_align": False,
            "snap_normal": (0, 0, 0),
            "gpencil_strokes": False,
            "cursor_transform": False,
            "texture_space": False,
            "remove_on_cancel": False,
            "view2d_edge_pan": False,
            "release_confirm": True,
            "use_accurate": False,
            "use_automerge_and_split": True,
        },
    )
    Edit_Mode()def Reset_origin():
    bpy.ops.object.origin_set(type="ORIGIN_CURSOR", center="MEDIAN")
obj = bpy.data.objects
mesh = bpy.data.meshes
Coll_obj = bpy.context.scene.collection.objects  #####
Active_obj = bpy.context.view_layer.objects
Delete = bpy.ops.mesh.delete                        #
     C O D E            #
                        #
    Clean()Create the base mesh : ” WAVE ” #
def create_WAVE():
    bpy.ops.mesh.primitive_grid_add(
        size=84, x_subdivisions=6, y_subdivisions=84, location=(0, 42, 0)
    )
    Resize(1 / 14, 1, 1)Using the 1/14 ratio in order to create a grid of 84x6 units (from the 84x84’s one)
    Reset_origin()
    obj["Grid"].name = "WAVE"
    grid = bpy.context.selected_objects[0]
    vertices = list(grid.data.vertices)Translating verts coord. to the “field” dimensions of the analysis (84x44 units) Multiplying each added dimensions (+2,-2,+15,-15,+2,-2) by 14 to match with the vector’s resized grid system
    for v in vertices:
        if v.co.x > 0.5:
            v.co.x += 2 * 14
    for v in vertices:
        if v.co.x < -0.5:
            v.co.x -= 2 * 14  #        | | | | | | |
    for v in vertices:  # From : |1|1|1|1|1|1|
        if v.co.x > 3 * 14:  #        | | | | | | |
            v.co.x += 15 * 14
    for v in vertices:
        if (
            v.co.x < -3 * 14
        ):  #        |   |                |   |   |                |   |
            v.co.x -= (
                15 * 14
            )  # to :   | 3 |       16       | 3 | 3 |       16       | 3 |
    for v in vertices:  #        |   |                |   |   |                |   |
        if v.co.x > 19 * 14:
            v.co.x += 2 * 14
    for v in vertices:
        if v.co.x < -19 * 14:
            v.co.x -= 2 * 14Sorting each X coord representative of a mirror action with the Y coords Then, allocating the step value (Z coord).
    def MirrorUpper_A(YCo, Step):
        for v in vertices:
            if int(v.co.x) == 3 * 14 and int(v.co.y) in YCo:
                v.co.z = Step    def MirrorUpper_B(YCo, Step):
        for v in vertices:
            if int(v.co.x) == 19 * 14 and int(v.co.y) in YCo:
                v.co.z = Step    def MirrorLower_A(YCo, Step):
        for v in vertices:
            if int(v.co.x) == -3 * 14 and int(v.co.y) in YCo:
                v.co.z = Step    def MirrorLower_B(YCo, Step):
        for v in vertices:
            if int(v.co.x) == -19 * 14 and int(v.co.y) in YCo:
                v.co.z = Step    MirrorLower_A([3], -1)
    MirrorUpper_B([3], 1)
    MirrorLower_B([10, 11], -2)
    MirrorUpper_A([10, 11], 2)
    MirrorLower_A([18, 19, 20], -3)
    MirrorUpper_B([18, 19, 20], 3)
    MirrorLower_B([27, 28, 29, 30], -4)
    MirrorUpper_A([27, 28, 29, 30], 4)
    MirrorLower_A([37, 38, 39, 40, 41], -5)
    MirrorUpper_B([37, 38, 39, 40, 41], 5)
    MirrorLower_B([48, 49, 50, 51, 52, 53], -6)
    MirrorUpper_A([48, 49, 50, 51, 52, 53], 6)
    MirrorLower_A([60, 61, 62, 63, 64, 65, 66], -7)
    MirrorUpper_B([60, 61, 62, 63, 64, 65, 66], 7)
    MirrorLower_B([73, 74, 75, 76, 77, 78, 79, 80], -8)
    MirrorUpper_A([73, 74, 75, 76, 77, 78, 79, 80], 8)Applying the definition for the selected verts (analysis based)
create_WAVE()Giving some thighness and smoothness to the Wave’s mesh with modifiers
Select_and_Active_obj("WAVE")
mod_add = bpy.ops.object.modifier_add
mod_app = bpy.ops.object.modifier_apply
mod = bpy.context.object.modifiers
mod_add(type="SOLIDIFY")
mod["Solidify"].thickness = 0.5
mod["Solidify"].offset = 0
mod_app(modifier="Solidify")
mod_add(type="SUBSURF")
mod["Subdivision"].levels = 3
mod_app(modifier="Subdivision")Generate the “CLOUDS” based on the Wave’s mesh #
Calling again the same definition to generate the starting mesh of the “clouds”
create_WAVE()
obj["WAVE.001"].name = "CLOUDS"
Select_and_Active_obj("CLOUDS")Applying different modifiers to generate a ew “noised” mesh which would surround the first one with variations (analysis based)
mod_add = bpy.ops.object.modifier_add
mod = bpy.context.object.modifiers
mod_add(type="DECIMATE")
mod["Decimate"].decimate_type = "UNSUBDIV"
mod["Decimate"].iterations = 1Adding texture to the displace modifiers to achieve the desired result
bpy.ops.texture.new()
tex = bpy.data.textures["Texture"]
tex.type = "CLOUDS"
tex.noise_basis = "VORONOI_F2"
tex.noise_scale = 10
tex.noise_depth = 0
tex.nabla = 0.1
tex.intensity = 0.4
tex.contrast = 2.9
tex.saturation = 0
mod_add(type="DISPLACE")
mod["Displace"].texture = tex
mod["Displace"].texture_coords = "GLOBAL"
mod["Displace"].direction = "NORMAL"
mod["Displace"].strength = 8Extruding and Resizing it in order to fit with Wave’s bondaries
Extrude_malifold(5.5)
Resize(1.2, 1.1, 1.5)
Translate(-1, 0, -6)
mod_add(type="SUBSURF")
mod["Subdivision"].levels = 3Create the 3D grid #
Using loops and ranges to create a 3D grid mesh (the cubic fragment only)
def Grid_of_points(rows, columns, levels):
    mesh_grid = mesh.new("Mesh_Grid")
    obj_grid = obj.new("GRID", mesh_grid)
    verts, edges = [], []
    i = 0
    for l in range(levels):
        for r in range(rows):
            for c in range(columns):
                verts.append((c, r, l))
                if c < columns - 1:
                    edges.append((i, i + 1))
                if r < rows - 1:
                    edges.append((i, i + columns))
                if l < levels - 1:
                    edges.append((i, i + (rows * columns)))
                i += 1
    mesh_grid.from_pydata(verts, edges, [])
    Coll_obj.link(obj_grid)
    obj_grid.name = "GRID"
    Select_and_Active_obj("GRID")
    Edit_Mode()
    Delete(type="EDGE_FACE")  # Deleting the edges and faces to keep the vertices only
    Edit_Mode()
Grid_of_points(20, 20, 20)Specifying the limits for the random cube’s location to always end up inside of the whole score
Fragment_x = random.randint(-22, 3)  #
Fragment_y = random.randint(0, 65)
Horizon_z = -9.5z = -10(+0.5) -> Adding +0.5 because the crosses’s cursors will be in the center of the geometry (and not at the bottom)
New_loc("GRID", Fragment_x, Fragment_y, Horizon_z)Relocating the grid to the randomly selected fragment
SELECTION IS INSIDE #
Select_and_Active_obj("GRID")Identifying which points of the GRID are inside the CLOUDS with a definition based on the fonction “closest point on a mesh” and on a max distance (from it)
def is_inside(points_B, max_dist, obj_A):
    result, points_A, normal, face = obj_A.closest_point_on_mesh(
        points_B, distance=max_dist
    )
    if not result:
        return False
    points_C = points_A - points_B
    v = points_C.dot(normal)
    return not (v < 0.0)
obj_A = obj["GRID"]
obj_B = obj["CLOUDS"]
Edit_Mode()
mesh = bmesh.from_edit_mesh(bpy.context.object.data)
obj_B_matrix = obj_B.matrix_world.inverted()
mat = obj_B_matrix @ obj_A.matrix_world
for v in mesh.verts:
    points_B = mat @ v.co
    if is_inside(points_B, 20, obj_B):
        v.select = TrueDeleting the selected points/verts
Delete(type="VERT")
Edit_Mode()
Deselect_All()Instancing CROSS #
Creating 1x1x1 cross from individual’s extrusion of each faces of a cube
bpy.ops.mesh.primitive_cube_add(scale=(0.1, 0.1, 0.1))
Edit_Mode()
bpy.ops.mesh.extrude_faces_move(TRANSFORM_OT_shrink_fatten={"value": 0.4})
Edit_Mode()
obj["Cube"].name = "CROSS"Select_and_Active_obj("GRID")
Select_obj("CROSS")
bpy.ops.object.parent_set(type="OBJECT")
bpy.context.object.instance_type = "VERTS"Relocating the crosses to the same origin as the grid
New_loc("CROSS", Fragment_x, Fragment_y, Horizon_z)Boolean Cube #
Cutting the “Wave’s” in the limits of the randomly selected fragment
bpy.ops.mesh.primitive_cube_add(size=20)
New_loc(
    "Cube", Fragment_x + 9.5, Fragment_y + 9.5, 0
)  # +9.5 because cube's origin is in the center of the geometry
Select_and_Active_obj("WAVE")
mod_add = bpy.ops.object.modifier_add
mod_app = bpy.ops.object.modifier_apply
mod = bpy.context.object.modifiers
mod_add(type="BOOLEAN")
mod["Boolean"].object = obj["Cube"]
mod["Boolean"].solver = "EXACT"
mod["Boolean"].operation = "INTERSECT"
mod_app(modifier="Boolean")Hiding useless objects #
bpy.data.objects["CLOUDS"].hide_set(True)
bpy.data.objects["Cube"].hide_set(True)
Deselect_All()Printing console #
print("---------------------------------------------")
print(" ")
print(" ")
print("  Generated fragment location :")
print(" ")
print("   x =", Fragment_x + 9.5)
print("   Y =", Fragment_y + 9.5)
print(" ")
Amplitude = obj["WAVE"].dimensions.z
print("  Wave Amplitude = ", round(Amplitude))
print(" ")
Select_and_Active_obj("GRID")
Edit_Mode()
mesh = bpy.context.view_layer.objects.active.data
bm = bmesh.from_edit_mesh(mesh)
print("  Cross amount =", len(bm.verts))
print("   -> Density =", len(bm.verts) / 8000)
Edit_Mode()
Deselect_All()
print(" ")
print(" ")